/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnnecessaryConditionCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-unnecessary-condition-check.md",
    description: "Disallow conditionals where the type is always truthy or always falsy.",
};

interface Options {
    allowConstantLoopConditions?: boolean;
    allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
}

interface LocationInfo {
    fileName: string;
    line: number;
    startCol: number;
    endCol: number;
    start: number;
    end: number;
    nameStr: string;
}

export class NoUnnecessaryConditionCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private defaultOptions: Options = {
        allowConstantLoopConditions: false,
        allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcherCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        };
        return [fileMatcherCb];
    }

    public check = (arkFile: ArkFile) => {
        if (arkFile instanceof ArkFile) {
            const code = arkFile.getCode();
            if (!code) {
                return;
            }
            const filePath = arkFile.getFilePath();
            const asRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);

            // 检查类型断言
            const locationInfos = this.checkCondition(asRoot, filePath);

            // 输出结果
            locationInfos.forEach(loc => {
                this.addIssueReportNodeFix(loc, filePath);
            });
        }
    }

    private checkCondition(sourceFile: ts.SourceFile, filePath: string): LocationInfo[] {
        const options: Options = this.rule && this.rule.option[0] ? this.rule.option[0] as Options : this.defaultOptions;
        const results: LocationInfo[] = [];

        const visit = (node: ts.Node) => {
            let condition: ts.Expression | undefined;

            if (ts.isIfStatement(node)) {
                condition = node.expression;
                if (ts.isPropertyAccessExpression(condition)) {
                    return;
                } else {
                    this.handleIfStatement(condition, sourceFile, results);
                }
            } else if (ts.isWhileStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isDoStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isForStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.condition;
            } else if (ts.isConditionalExpression(node)) {
                condition = node.condition;
            } else if (ts.isOptionalChain(node)) {
                condition = node.expression;
            } else if (ts.isCallExpression(node)) {
                condition = this.handleCallExpression(node, sourceFile);
            } else if (ts.isBinaryExpression(node)) {
                condition = this.handleBinaryExpression(node, sourceFile, results, options);
            }

            if (condition) {
                this.handleGeneralCondition(condition, sourceFile, node, results);
            }

            ts.forEachChild(node, visit);
        };

        visit(sourceFile);
        return results;
    }

    private handleIfStatement(condition: ts.Expression, sourceFile: ts.SourceFile, results: LocationInfo[]): void {
        if (this.isAlwaysTruthyOrFalsy(condition)) {
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
            const assertionName = condition.getText();
            const start = condition.getStart();
            const end = condition.getEnd();
            const endCharacter = character + assertionName.length;
            results.push({
                fileName: sourceFile.fileName,
                line: line + 1,
                startCol: character + 1,
                endCol: endCharacter + 1,
                start: start,
                end: end,
                nameStr: assertionName
            });
        }
    }

    private handleCallExpression(node: ts.CallExpression, sourceFile: ts.SourceFile): ts.Expression | undefined {
        const expression = node.expression;
        const args = node.arguments;
        const arrayPredicate = new Set(['filter', 'find', 'some', 'every']);
        if (ts.isPropertyAccessExpression(expression) && arrayPredicate.has(expression.name.text)) {
            const callback = args[0];
            if (callback && ts.isArrowFunction(callback) && ts.isIdentifier(callback.body)) {
                return callback.body;
            }
        }
        return undefined;
    }

    private handleBinaryExpression(node: ts.BinaryExpression, sourceFile: ts.SourceFile, results: LocationInfo[], options: Options): ts.Expression | undefined {
        const operatorToken = node.operatorToken.kind;

        if (operatorToken === ts.SyntaxKind.QuestionQuestionEqualsToken || operatorToken === ts.SyntaxKind.BarBarEqualsToken ||
            operatorToken === ts.SyntaxKind.AmpersandAmpersandEqualsToken || operatorToken === ts.SyntaxKind.QuestionQuestionToken) {

            const leftType = this.getExpressionType(node.left);
            const isLeftAlwaysNullOrUndefined = this.isAlwaysNull(leftType) || this.isAlwaysUndefined(leftType);
            if (!isLeftAlwaysNullOrUndefined || (operatorToken === ts.SyntaxKind.AmpersandAmpersandEqualsToken && isLeftAlwaysNullOrUndefined)) {
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.left.getStart());
                const assertionName = node.left.getText();
                const start = node.left.getStart();
                const end = node.left.getEnd();
                const endCharacter = character + assertionName.length;
                results.push({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    startCol: character + 1,
                    endCol: endCharacter + 1,
                    start: start,
                    end: end,
                    nameStr: assertionName
                });
            }
            return undefined;
        } else {
            return node;
        }
    }

    private handleGeneralCondition(condition: ts.Expression, sourceFile: ts.SourceFile, node: ts.Node, results: LocationInfo[]): void {
        if (!this.checkConditionType(sourceFile, node, condition)) {
            return;
        }
        let start: number = 0;
        let end: number = 0;
        let position: ts.LineAndCharacter | undefined;
        let assertionName: string = '';

        if (ts.isBinaryExpression(condition)) {
            const rightType = this.getExpressionType(condition.right);
            const isRightAlwaysFalsy = this.isAlwaysFalsy(rightType);
            if (isRightAlwaysFalsy) {
                position = sourceFile.getLineAndCharacterOfPosition(condition.right.getStart());
                assertionName = condition.right.getText();
                start = condition.right.getStart();
                end = condition.right.getEnd();
            } else {
                position = sourceFile.getLineAndCharacterOfPosition(condition.left.getStart());
                assertionName = condition.left.getText();
                start = condition.left.getStart();
                end = condition.left.getEnd();
            }
        } else if (ts.isOptionalChain(condition)) {
            if (ts.isPropertyAccessExpression(condition) ||
                ts.isElementAccessExpression(condition) || ts.isCallExpression(condition)) {
                const questionDotToken = condition.questionDotToken;
                if (questionDotToken) {
                    start = questionDotToken.getStart();
                    end = questionDotToken.getEnd();
                    position = sourceFile.getLineAndCharacterOfPosition(questionDotToken.getStart());
                    assertionName = questionDotToken.getText();
                }
            }
        } else {
            start = condition.getStart();
            end = condition.getEnd();
            position = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
            assertionName = condition.getText();
        }
        this.pushResults(position, assertionName, sourceFile.fileName, start, end, results);
    }

    private pushResults(position: ts.LineAndCharacter | undefined,
        assertionName: string, fileName: string, start: number, end: number,
        results: LocationInfo[]): void {
        if (position) {
            const endCharacter = position.character + assertionName.length;
            results.push({
                fileName: fileName,
                line: position.line + 1,
                startCol: position.character + 1,
                endCol: endCharacter + 1,
                start: start,
                end: end,
                nameStr: assertionName
            });
        }
    }

    private checkConditionType(sourceFile: ts.SourceFile, node: ts.Node, condition: ts.Expression): boolean {
        // 布尔字面量检查
        if (condition.kind === ts.SyntaxKind.TrueKeyword || condition.kind === ts.SyntaxKind.FalseKeyword) {
            return true; // 布尔字面量总是为真或为假
        }

        // 二元表达式检查
        if (ts.isBinaryExpression(condition)) {
            const leftType = this.getExpressionType(condition.left);
            const rightType = this.getExpressionType(condition.right);

            if (condition.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
                // && 操作符
                const isLeftAlwaysFalsy = this.isAlwaysFalsy(leftType);
                const isRightAlwaysFalsy = this.isAlwaysFalsy(rightType);
                return isLeftAlwaysFalsy || isRightAlwaysFalsy;
            } else if (condition.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
                // || 操作符
                const isLeftAlwaysTruthy = this.isAlwaysTruthy(leftType);
                const isRightAlwaysTruthy = this.isAlwaysTruthy(rightType);
                return isLeftAlwaysTruthy || isRightAlwaysTruthy;
            } else {
                // 检查是否比较 undefined 或 null
                const isUndefinedComparison = this.isAlwaysUndefined(leftType) || this.isAlwaysUndefined(rightType);
                const isNullComparison = this.isAlwaysNull(leftType) || this.isAlwaysNull(rightType);
                return (isUndefinedComparison || isNullComparison);
            }
        }

        // 基本类型检查
        const conditionType = this.getExpressionType(condition);
        if (conditionType === 'number' || conditionType === 'string' || conditionType === 'boolean') {
            return true;
        }

        // 函数类型检查
        if (conditionType === 'function') {
            return !this.isIdentifierHasNullOrUndefined(sourceFile, condition.getText(), "function");
        }

        // any 类型检查
        if (conditionType === 'any') {
            const isIdentifierHasNullOrUndefined = this.isIdentifierHasNullOrUndefinedAny(node, condition.getText());
            return !isIdentifierHasNullOrUndefined;
        }

        return false;
    }

    private getExpressionType(expression: ts.Expression): string {
        if (expression.kind === ts.SyntaxKind.TrueKeyword || expression.kind === ts.SyntaxKind.FalseKeyword) {
            return 'boolean';
        } else if (expression.kind === ts.SyntaxKind.NumericLiteral) {
            return 'number';
        } else if (expression.kind === ts.SyntaxKind.StringLiteral) {
            return 'string';
        } else if (expression.kind === ts.SyntaxKind.NullKeyword) {
            return 'null';
        } else if (expression.kind === ts.SyntaxKind.UndefinedKeyword) {
            return 'undefined';
        } else if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.ExclamationToken) {
            return 'boolean'; // 假设一元运算符 ! 的结果是布尔类型
        } else if (ts.isBinaryExpression(expression)) {
            return 'boolean'; // 假设二元运算符的结果是布尔类型
        } else if (ts.isIdentifier(expression)) {
            // 对于标识符，无法直接获取类型，但可以返回标识符名称
            return 'identifier';
        } else {
            return 'unknown';
        }
    }
    private isAlwaysTruthyOrFalsy(expression: ts.Expression): boolean {
        // 布尔字面量
        if (expression.kind === ts.SyntaxKind.TrueKeyword) {
            return true; // true 是总是为真
        } else if (expression.kind === ts.SyntaxKind.FalseKeyword) {
            return true; // false 是总是为假
        }

        // 数字字面量
        if (expression.kind === ts.SyntaxKind.NumericLiteral) {
            const value = parseFloat(expression.getText());
            return value === 0 ? true : false; // 0 是 falsy，其他数字是 truthy
        }

        // 字符串字面量
        if (expression.kind === ts.SyntaxKind.StringLiteral) {
            const text = expression.getText();
            return text === '' ? true : false; // 空字符串是 falsy，其他字符串是 truthy
        }

        // null 和 undefined
        if (expression.kind === ts.SyntaxKind.NullKeyword) {
            return true; // null 是 falsy
        } else if (expression.kind === ts.SyntaxKind.UndefinedKeyword) {
            return true; // undefined 是 falsy
        }

        // 一元运算符（如 !）
        if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.ExclamationToken) {
            const operand = expression.operand;
            return this.isAlwaysTruthyOrFalsy(operand); // 递归检查操作数
        }

        // 二元运算符（如 && 和 ||）
        if (ts.isBinaryExpression(expression)) {
            const left = expression.left;
            const right = expression.right;
            const operator = expression.operatorToken.kind;

            if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
                // && 操作符
                return this.isAlwaysTruthyOrFalsy(left) || this.isAlwaysTruthyOrFalsy(right);
            } else if (operator === ts.SyntaxKind.BarBarToken) {
                // || 操作符
                return this.isAlwaysTruthyOrFalsy(left) || this.isAlwaysTruthyOrFalsy(right);
            }
        }

        // 其他情况默认返回 false
        return false;
    }

    private isAlwaysTruthy(type: string): boolean {
        return type === 'boolean' || type === 'number' || type === 'string';
    }

    private isAlwaysFalsy(type: string): boolean {
        return type === 'null' || type === 'undefined';
    }

    private isAlwaysUndefined(type: string): boolean {
        return type === 'undefined';
    }

    private isAlwaysNull(type: string): boolean {
        return type === 'null';
    }

    private isIdentifierHasNullOrUndefined(sourceFile: ts.SourceFile, identifier: string, type: string): boolean {
        for (let stmtNode of sourceFile?.statements ?? []) {
            if (!ts.isVariableStatement(stmtNode)) {
                continue;
            }
            let variableList = stmtNode.declarationList;
            if (variableList && variableList.declarations) {
                for (let declaration of variableList.declarations) {
                    if (!ts.isVariableDeclaration(declaration)) {
                        continue;
                    }
                    let nameIdentifier = declaration.name;
                    if (ts.isIdentifier(nameIdentifier) && nameIdentifier.escapedText === identifier) {
                        if (declaration.initializer?.getText().includes("null") || declaration.initializer?.getText().includes("undefined")) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private isIdentifierHasNullOrUndefinedAny(node: ts.Node, identifier: string): boolean {
        // 遍历父节点语句
        let parentNode = node.parent;
        while (parentNode) {
            // 递归检查每个语句节点
            if (this.checkNode(parentNode, identifier)) {
                return true;
            }
            parentNode = parentNode.parent;
        }
        return false;
    }
    private checkNodeVariableStatement(node: ts.VariableStatement, identifier: string): boolean {
        for (const declaration of node.declarationList.declarations) {
            // 检查变量名是否匹配
            if (ts.isIdentifier(declaration.name) && declaration.name.text === identifier) {
                // 检查初始化值是否为 null 或 undefined
                if (declaration.initializer && this.isNullOrUndefined(declaration.initializer)) {
                    return true;
                }
            }
        }
        return false;
    }
    private checkNodeFunction(i: number, parameters: ts.NodeArray<ts.ParameterDeclaration>, identifier: string): boolean {
        const parameter = parameters[i];
        if (parameter.name.getText() === identifier) {

            if (parameter.type) {
                const parameterType = parameter.type?.getText()?.toString();
                if (parameterType.includes('null') || parameterType.includes('undefined')) {
                    return true;
                }
            }
        }
        return false;
    }

    private checkNodeIdentifier(node: ts.BinaryExpression, identifier: string): boolean {
        // 检查赋值的左侧是否为目标标识符
        if (ts.isIdentifier(node.left) && node.left.text === identifier) {
            // 检查赋值的右侧是否为 null 或 undefined
            if (this.isNullOrUndefined(node.right)) {
                return true;
            }
        }
        return false;
    }
    private checkNode(node: ts.Node, identifier: string): boolean {
        // 如果当前节点是变量声明
        if (ts.isVariableStatement(node) && this.checkNodeVariableStatement(node, identifier)) {
            return true;
        }
        // 如果当前节点是函数声明或方法声明
        if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
            const parameters = node.parameters;
            for (let i = 0; i < parameters.length; i++) {
                if (this.checkNodeFunction(i, parameters, identifier)) {
                    return true;
                }
            }
        }
        // 如果当前节点是表达式语句
        else if (ts.isExpressionStatement(node)) {
            if (this.isNullOrUndefined(node.expression)) {
                return true;
            }
        }
        // 如果当前节点是赋值表达式
        else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
            if (this.checkNodeIdentifier(node, identifier)) {
                return true;
            }
        }
        // 递归检查子节点
        for (const child of node.getChildren()) {
            this.checkNode(child, identifier);
        }
        return false;
    }

    private isNullOrUndefined(node: ts.Node): boolean {
        // 检查节点是否为 null 或 undefined
        return (ts.isIdentifier(node) && (node.text.includes('null') || node.text.includes('undefined')));
    }

    // 创建修复对象 
    private ruleFix(loc: LocationInfo): RuleFix {
        const [start, end] = this.getFixRange(loc);
        return { range: [start, end], text: '.' };
    }

    // 获取起始位置和结束位置
    private getFixRange(loc: LocationInfo): [number, number] {
        const startPosition = loc.start;
        const endPosition = loc.end;
        return [startPosition, endPosition];
    }

    private addIssueReportNodeFix(loc: LocationInfo, filePath: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(loc.line, loc.startCol, loc.endCol, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        if (loc.nameStr === '?.') {
            let fix: RuleFix = this.ruleFix(loc);
            this.issues.push(new IssueReport(defect, fix));
        } else {
            this.issues.push(new IssueReport(defect, undefined));
        }
        RuleListUtil.push(defect);
    }
}